﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MipsMachine
{
	/// <summary>
	/// Defines all opcodes for I-Type and J-Type instructions instructions
	/// </summary>
	public enum OpCode : uint
	{
		Data = 0xFFFFFFFF,	// Placeholder for data compilation opcode
							// Should not be passed to the virtual machine, as this is an invalid opcode.
		Undefined = 0,
		ADDI = 0x20000000,
		ADDU = 0x21000000,
		ANDI = 0x30000000,
		BEQ = 0x10000000,
		BLTZ = 0x40000000,
		BNE = 0x14000000,
		J = 0x08000000,
		JMP = J,	// Alias Jump
		JAL = 0x0C000000,
		LB = 0x80000000,
		LUI = 0x3C000000,
		LW = 0x8C000000,
		ORI = 0x34000000,
		SB = 0xA0000000,
		SLTI = 0x28000000,
		SLTIU = 0x2C000000,
		SW = 0xAC000000,
		XORI = 0x38000000
	}
	/// <summary>
	/// Defines function value for R-Type instructions
	/// </summary>
	public enum Function
	{
		ADD = 0x20,
		AND = 0x24,
		DIV = 0x1A,
		DIVU = 0x1B,
		JR = 0x08,
		MFHI = 0x10,
		MFLO = 0x12,
		MULT = 0x18,
		MULTU = 0x19,
		OR = 0x25,
		SLL = 0x00,
		SLLV = 0x04,
		SLT = 0x2A,
		SLTU = 0x2B,
		SRA = 0x03,
		SRL = 0x02,
		SRLV = 0x06,
		SUB = 0x22,
		SUBU = 0x23,
		SYSCALL = 0x0C,
		XOR = 0x26,
		// Pseudo instructions
		DataByte = 0x0100,
		DataWord = 0x0200,
		DataDword = 0x0300,
		LD = 0x1000,	// Load dword
		SD = 0x2000,	// Store dword

		LID = 0x3000,	// Load 32-bit immediate
		SID = 0x4000,	// Store 32-bit immediate
	}
	/// <summary>
	/// Defines the different types of instructions
	/// </summary>
	public enum InstructionType
	{
		IType,	// Immediate type
		RType,	// Register type
		JType,	// Branch type
		CoType	// Instruction is for co-processor
	}
	/// <summary>
	/// Defines all general purpose registers
	/// </summary>
	public enum Register
	{
		zero = 0,
		at,
		v0, v1,
		a0, a1, a2, a3,
		t0,	t1,	t2,	t3, t4,	t5,	t6,	t7,	// temporary
		s0, s1, s2, s3, s4, s5, s6, s7,
		t8, t9,
		k0, k1,
		gp,	// Global pointer
		sp,	// Stack pointer
		fp,	// Frame pointer
		ra
	}
	/// <summary>
	/// Specifies a ready-to-be-parsed instruction.
	/// </summary>
	public class MipsMetaInstruction
	{
		private string label;
		private OpCode opcode;
		private byte[] raw_data;
		private Function funct;
		private string rs;
		private string rt;
		private string rd;
		private string immediate;
		private int line;
		private string file;


		/// <summary>
		/// Defines a label at this code line
		/// </summary>
		public string Label { get { return label; } set { label = value; } }
		/// <summary>
		/// Gets or sets the opcode for this instruction
		/// </summary>
		public OpCode OpCode { get { return opcode; } set { opcode = value; } }
		/// <summary>
		/// Gets or sets the Funct field for this instruction
		/// </summary>
		public Function Funct { get { return funct; } set { funct = value; } }
		/// <summary>
		/// Gets or sets the source register for this instruction
		/// </summary>
		public string Rs { get { return rs; } set { rs = value; } }
		/// <summary>
		/// Gets or sets the targett register for this instruction
		/// </summary>
		public string Rt { get { return rt; } set { rt = value; } }
		/// <summary>
		/// Gets or sets the destination register for this instruction
		/// </summary>
		public string Rd { get { return rd; } set { rd = value; } }
		/// <summary>
		/// Gets or sets the immediate value for this instruction
		/// </summary>
		/// 
		public string Immediate { get { return immediate; } set { immediate = value; } }
		/// <summary>
		/// Gets or sets the code line for this instruction (for debugging purposes)
		/// </summary>
		public int Line { get { return line; } set { line = value; } }

		public string File { get { return file; } set { file = value; } }

		public byte[] RawData { get { return raw_data; } set { raw_data = value; } }
		public MipsMetaInstruction(OpCode op, Function func, string lb, string rd, string rs, string rt, string imm, int line)
		{
			opcode = op;
			funct = func;
			label = lb;
			this.rs = rs;
			this.rt = rt;
			this.rd = rd;
			immediate = imm;
			this.line = line;
		}
	}
	/// <summary>
	/// Specifies a binary mips instruction
	/// </summary>
	public struct MipsInstruction
	{
		uint data;
		/// <summary>
		/// Gets or sets the opcode
		/// </summary>
		public OpCode OpCode
		{
			get
			{
				return (OpCode)(data & (0xFC000000));
			}
			set
			{
				data = ((uint)value) | (data & 0x03FFFFFF);
			}
		}
		/// <summary>
		/// Gets or sets the source register
		/// </summary>
		public Register Rs
		{
			get
			{
				return (Register)((data >> 21) & 0x1f);
			}
			set
			{
				data = ((uint)value << 21) | (data & 0xFC1FFFFF);
			}
		}
		/// <summary>
		/// Gets or sets the target register
		/// </summary>
		public Register Rt
		{
			get
			{
				return (Register)((data >> 16) & 0x1F);
			}
			set
			{
				data = ((uint)value << 16) | (data & 0xFFE0FFFF);
			}
		}
		/// <summary>
		/// Gets or sets the destination register
		/// </summary>
		public Register Rd
		{
			get
			{
				return (Register)((data >> 11) & 0x1F);
			}
			set
			{
				data = ((uint)value << 11) | (data & 0xFFFF07FF);
			}
		}
		/// <summary>
		/// Gets or sets the Shift field for R-Type instructions
		/// </summary>
		public uint Shamt
		{
			get
			{
				return data & 0x000007C0 >> 6;
			}
			set
			{
				data = value << 6 | (data & 0xFFFFF83F);
			}
		}

        public int GetSignedImmediate()
        {
            short imm = (short)this.Immediate;
            return (int)imm;
        }


		/// <summary>
		/// Gets or sets the Funct field for R-Type instructions
		/// </summary>
		public Function Funct
		{
			get
			{
				return (Function)(data & 0x3F);
			}
			set
			{
                data = ((uint)value & 0x3F) | (data & 0xFFFFFFC0);
			}
		}
		/// <summary>
		/// Gets or sets the immediate value for I-Type instructions
		/// </summary>
		public uint Immediate
		{
			get
			{
				return data & 0xFFFF;
			}
			set
			{
				data = (value & 0xffff) | (data & 0xFFFF0000);
			}
		}
		/// <summary>
		/// Gets or sets the address for J-Type instructions
		/// </summary>
		public uint Address
		{
			get
			{
				return data & 0x3FFFFFF;
			}
			set
			{
				data = (value & 0x3F) | (data & 0xFC000000);
			}
		}
		/// <summary>
		/// Gets the type of instruction. This is inferred from the opcode field.
		/// </summary>
		public InstructionType Type
		{
			get
			{
				if ((data & 0xFC000000) == 0)
					return InstructionType.RType;
				else if ((data & 0xF8000000) == 0x08000000)
					return InstructionType.JType;
				else if ((data & 0x3C000000) == 0x40000000)
					return InstructionType.CoType;
				else
					return InstructionType.IType;
			}
		}
		// The following functions provide conversion operations
		public static explicit operator int(MipsInstruction ins)
		{
			return (int)ins.data;
		}
		public static explicit operator MipsInstruction(uint val)
		{
			return new MipsInstruction(val);
		}
		public static explicit operator uint(MipsInstruction ins)
		{
			return ins.data;
		}
		public static explicit operator MipsInstruction(int val)
		{
			return new MipsInstruction((uint)val);
		}
		public MipsInstruction(uint instruction)
		{
			data = instruction;
		}

		private static void Swap<T>(ref T a, ref T b)
		{
			T tmp = a;
			a = b;
			b = tmp;
		}
		/// <summary>
		/// Swaps the endianness in the structure
		/// </summary>
		public MipsInstruction SwapEndian()
		{
			var data = BitConverter.GetBytes(this.data);
			Swap(ref data[0], ref data[1]);
			Swap(ref data[1], ref data[2]);
			Swap(ref data[2], ref data[3]);
			return new MipsInstruction(BitConverter.ToUInt32(data, 0));
		}
		/// <summary>
		/// Generates a mockup disassembly code of the instruction for easier debugging
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			switch (Type)
			{
				case InstructionType.RType:
					return Funct.ToString() + " $" + Rd.ToString() + ", $" + Rs.ToString() + ", $" + Rt.ToString();
				case InstructionType.IType:
					return OpCode.ToString() + " $" + Rs.ToString() + ", $" + Rt.ToString() + ", " + Immediate.ToString("x");
				case InstructionType.JType:
					return OpCode.ToString() + " 0x" + Address.ToString("x");
				case InstructionType.CoType:
					return "(CoProcessor)";
				default:
					return "Invalid instruction";
			}
		}
	}
}
